"""
    Created 24-06-2011
    :autor: jlucasgl
"""

from lxml import etree
#from lxml.etree import XMLSyntaxError
from sys import exc_info, exit


import config


class XMLElementNotFound(Exception):
    """ This class is raised when a element in the XML tree is searched 
        and not found
    """
    def __init__(self, name):
        """ Recieve the name attribute to display a more understandable message
        """
        self.name = name

    def __str__(self):
        return "XML element with name '" + str(self.name) + "' not found"


class XMLParser(object):
    """ Parse the ``.xml`` file, and it validates against the `schema.xsd`
    """

    def __init__(self, xmlfilename):
        """ Open xml file, validate against a ``schema.xsd``
            and prepare iteration functions
        """
        xmlparser = self.parse_schema()
        self.parse_xml(xmlfilename, xmlparser)
        self.root = self.xmltree.getroot()

    def parse_schema(self):
        try:
            self.schema = self.get_schema()
            return etree.XMLParser(schema = self.schema)
        except etree.XMLSyntaxError as inst:
            print "XMLSyntaxError in XMLSchema:\n\t", inst
            exit()

    def parse_xml(self, xmlfilename, xmlparser = None):
        """ Parse XML with xmlparser and validate againts the schema

            :param xmlfilename: XML file witch is going to be parsed
            :param xmlparser: Waits a etree.XMLSchema object. Default is parser
                              without schema validation
        """
        self.xmlfilename = xmlfilename
        try:
            self.xmltree = etree.parse(xmlfilename, parser = xmlparser)
        except etree.XMLSyntaxError as inst:
            print "XMLSyntaxError in XML<" + xmlfilename + ">:\n\t", inst
            exit()            


    def get_schema(self, xsd=config.WORKDIR + '/Tools/DescriptionFile/schema.xsd'):
        """ Parse XML Schema and return XMLSchema object

            :param xsd: route of XMLSchema file. Default is ``Tools.DescriptionFile.schema.xsd``
            :return: etree.XMLSchema object
        """
        root_schema = etree.parse(xsd)
        return etree.XMLSchema(root_schema)
         
    def validate_xml(self, xsd=None):
        """ Validate xml file against a xml schema

            :param xsd: route of XMLSchema file. Default is ``Tools.DescriptionFile.schema.xsd``
            :return: true if evrything's ok else false
        """
        if xsd:
            schema = get_schema(xsd)
            return schema.validate(self.root)
        else:
            return self.schema.validate(self.root)

    def __str__(self):
        """ 
    
            :return: xml file as a string
        """
        return etree.tostring(self.xmltree.getroot(), encoding="UTF-8", xml_declaration=True, pretty_print=True)


class XMLManager(XMLParser):
    """ Parse the ``.xml`` file, it validates against the `schema.xsd` and manage the XML tree
    """

    def __init__(self, xmlfilename):
        """ Open xml file, validate against a ``schema.xsd``
            and prepare iteration functions
        """
        self.cluster = etree._Element()
        self.region = etree._Element()
        self.element = etree._Element()
        self.transform = etree._Element()
        self.xmltree = etree._ElementTree()
        self.xmlfilename = xmlfilename

        super(XMLManager, self).__init__(xmlfilename)

        # Prepare iteration functions
        self.prepare_iter_func()

    def get_clusters(self):
        return self.xmltree.findall("cluster")


    def prepare_iter_func(self):
        """ Reset cluster iterator and advances it once
        """
        self.reset_iter_cluster()
        self.next_cluster()

    # -------------- Search funtions ---------------------------

    def find_cluster(self, clustername):
        """ Position iterators to locate clustername in xml tree

            :return: etree.ElementTree if cluster is found, else raise XMLElementNotFound exception
        """
        cluster = self.xmltree.find("/cluster[@name='" + clustername + "']")
        if (cluster is not None):
            self.set_cluster(cluster)
            return cluster
        else:
            raise XMLElementNotFound(clustername)

    def find_region(self, regionname):
        """ Position iterator to locate regionname in xml tree

            :return: etree.ElementTree if region is found, else raise XMLElementNotFound exception
        """
        region = self.cluster.find("region[@name='" + regionname + "']")
        if (region is not None):
            self.set_region(region)
            return region
        else:
            return XMLElementNotFound(regionname)

    def find_element(self, elementname):
        """ Position iterator to locate elementname in xml tree

            :return: etree.ElementTree if element is found, else raise XMLElementNotFound exception
        """
        element = self.region.find("element[@name='" + elementname + "']")
        if (element is not None):
            self.set_element(element)
            return element
        else:
            return XMLElementNotFound(elementname)

    # -------------- Position elements functions --------------

    def set_cluster(self, cluster):
        self.cluster = cluster
        self.reset_iter_region()

    def set_region(self, region):
        self.region = region
        self.reset_iter_element()
    
    def set_element(self, element):
        self.element = element

    # ---------------- Reset iter generators ----------------

    def reset_iter_cluster(self):
        self.iter_cluster = self.xmltree.iterfind("cluster")

    def reset_iter_region(self):
        self.iter_region = self.cluster.iterfind("region")

    def reset_iter_element(self):
        self.iter_element = self.region.iterfind("element")

    def reset_iter_transform(self):
        self.iter_transform = self.element.iterfind(".//mutator")

    # --------------- Element iterator functions ---------------

    def next_cluster(self):
        """ Iterate over clusters
            
            :return: etree.ElementTree if previous cluster was not the last else None
        """
        try:
            self.cluster = self.iter_cluster.next()
        except StopIteration:
            self.reset_iter_cluster()
            return None

        self.reset_iter_region()
        return self.cluster

    def next_region(self):
        """ Iterate over regions

            :return: etree.ElementTree if previous region was not the last else None
        """
        try:
            self.region = self.iter_region.next()
        except StopIteration:
            self.reset_iter_region()
            return None

        self.reset_iter_element()
        return self.region

    def next_element(self):
        """ Iterate over elements

            :return: etree.ElementTree if previous element was not the last else None
        """
        try:
            self.element = self.iter_element.next()
        except StopIteration:
            self.reset_iter_element()
            return None
        self.reset_iter_transform()
        return self.element

    def next_transform(self):
        """ Iterate over mutators

            :return: etree.ElementTree if previous mutators was not the last else None
        """
        try:
            self.transform = self.iter_transform.next()
        except StopIteration:
            self.reset_iter_transform()
            return None
        return self.transform


